02 January 2019

Although it’s possible to use Shiro in any type of enterprise application, here we focus on enterprise Spring-based applications. In such these applications, JPA is commonly used to implement the data layer. This means we present users, roles, and permissions through three entity classes as follows.

@Entity
@Table(name="user_tbl")
public class User{
   String username;
   String password;
   List<Role> roles;
}

@Entity
@Table(name="role_tbl")
public class Role{
    String name;
    List<Permission> permissions;
}

@Entity
@Table(name="permission_tbl")
public class Permission{
	String name;
}

Normally, we implement an individual DAO object for each specific entity class. So, we will have UserDAO, RoleDAO, and PermissionDAO classes in our project.

@Repository
public interface UserDAO extends CrudRepository{
}

And normally, we implement a service class to bind dao logic to transactions.

@Service
@Transactional
public class UserService{
 	
	@Autowired
	UserDAO userDAO;

	public User save(User user){
		return userDAO.save(user);
	}

	public User findByUsernameAndPassowrd(String username, String password){
		return userDAO.findByUsernameAndPassowrd(username, password);
	}
}

Now, we need to refactor the Realm class to utilize our service classes to authenticate/authorize users. In this case we extend org.apache.shiro.realm.jdbc.AuthorizingRealm instead of JdbcRealm and use our service object to provide security principals. Here is the code.

package com.ahmadsedi.shiro.web.security;

import com.ahmadsedi.shiro.web.entity.Permission;
import com.ahmadsedi.shiro.web.entity.User;
import com.ahmadsedi.shiro.web.service.PermissionService;
import com.ahmadsedi.shiro.web.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author Ahmad R. Seddighi (ahmadseddighi@yahoo.com)
 *         Date: 3/5/19
 *         Time: 9:22 AM
 */
@Component
public class JpaShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        String username = (String) getAvailablePrincipal(principals);

        User user = userService.findByUsername(username);
        Set<String> roleNames = new HashSet<>();
        user.getRoles().forEach(role -> roleNames.add(role.getName()));

        List<Permission> permissions = permissionService.findByRole(user.getRoles());
        Set<String> permissionKeys = new HashSet<>();
        permissions.forEach(permission -> permissionKeys.add(permission.getKey()));

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
        info.setStringPermissions(permissionKeys);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        String username = upToken.getUsername();
        AuthenticationInfo info = buildAuthenticationInfo(username, userService.findByUsername(username).getPassword().toCharArray());
        return info;
    }

    protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) {
        return new SimpleAuthenticationInfo(username, password, getName());
    }


    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

In this extended class we have to override doGetAuthenticationInfo(), doGetAuthorizationInfo() which authenticates a user and returns a AuthenticationInfo and authorize a user and returns an AuthorizationInfo object respectively.

web.xml

Since we are working on a web application, we need to control web request to handle security. This means we need a web filter in our web.xml file to delegate all requests to Shiro to do all security stuff. For this purpose, Spring provides the filter DelegatingFilterProxy in web module.

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Now, we have to configure a bean in Spring context with the same name as the above filter, which refer to ShiroFilter. The following shows our Spring configuration.

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="jpaShiroRealm" />
</bean>

<!-- Configuring Apache Shiro in Spring-->
<!-- Name of the bean should be same as spring filter in web.xml -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <property name="loginUrl" value="/login.html"/>
    <property name="successUrl" value="/home.html"/>
    <property name="unauthorizedUrl" value="/access-denied.html"/>
    <property name="filterChainDefinitions">
        <value>
            [main]
            authc.loginUrl = /login.html
            sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
            # configure properties (like session timeout) here if desired

            # Use the configured native session manager:
            securityManager.sessionManager = $sessionManager
            [urls]

            /anonymous.html = anon
            /protected.html = authc
            /authorized-with-role.html = authc, roles[admin]
            /authorized-with-permission.html = authc, perms["admin:admin"]
        </value>
    </property>
</bean>

<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

That’s it; Full source code of this article can be found on my GitHub repository.